package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private UserMapper um;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Result login(LoginFormDTO dto, HttpSession session) {
        //1.验证手机和验证码是否匹配
        /*String code = (String) session.getAttribute(dto.getPhone());*/
        //修改：从redis中去获取到验证码
        ValueOperations valueOperations = redisTemplate.opsForValue();
        String code = (String) valueOperations.get(dto.getPhone());


        if (dto.getCode().equals(code) == false) {
            return Result.fail("输入的验证码有误或验证码已过期");
        }

        //2.根据手机号到数据库中查询用户是否存在
        String phone = dto.getPhone();
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getPhone, phone);
        User user = um.selectOne(wrapper);

        //用户不存在时执行
        if (user == null) {
            User newUser = new User();
            newUser.setPhone(dto.getPhone());
            newUser.setNickName("user_" + RandomUtil.randomString(10));
            int insert = um.insert(newUser);
            log.info("成功存入{}条数据", insert);
            // TODO:将user信息存入redis中去，方便验证登录状态
            //1.把user对象转成userDto
            UserDTO userDTO = BeanUtil.copyProperties(newUser, UserDTO.class);
            //2.设置一下token
            String token = UUID.randomUUID().toString();
            //4.将userDto存入redis中去
            redisTemplate.opsForHash().put("user_Login_token" + token, "id", String.valueOf(userDTO.getId()));
            redisTemplate.opsForHash().put("user_Login_token" + token, "nickName", userDTO.getNickName());
            redisTemplate.opsForHash().put("user_Login_token" + token, "icon", userDTO.getIcon());
            /*redisTemplate.opsForHash().putAll(token,userDtoMap);*/
            /*
             * 设置一下user登录校验的token的存活时间，没有操作时后的30分钟才会自动删除
             * 如果一直有操作，那么每次校验登录状态时，都会刷新时间，重新获得30分钟存在时间
             * */
            redisTemplate.expire("user_Login_token" + token, 10, TimeUnit.MINUTES);
            return Result.ok(token);

        }
        //用户存在时执行
        /* session.setAttribute("user", user);*/
        // TODO:将user信息存入redis中去，方便验证登录状态
        //1.把user对象转成userDto
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        //2.设置一下token
        String token = UUID.randomUUID().toString();
        //4.将userDto存入redis中去
        redisTemplate.opsForHash().put("user_Login_token" + token, "id", String.valueOf(userDTO.getId()));
        redisTemplate.opsForHash().put("user_Login_token" + token, "nickName", userDTO.getNickName());
        redisTemplate.opsForHash().put("user_Login_token" + token, "icon", userDTO.getIcon());
        /*redisTemplate.opsForHash().putAll(token,userDtoMap);*/
        /*
         * 设置一下user登录校验的token的存活时间，没有操作时后的30分钟才会自动删除
         * 如果一直有操作，那么每次校验登录状态时，都会刷新时间，重新获得30分钟存在时间
         * */
        redisTemplate.expire("user_Login_token" + token, 10, TimeUnit.MINUTES);
        return Result.ok(token);

    }

    @Override
    public Result logout(String token) {
        //退出功能，则把redis中的token数据给删除
        redisTemplate.delete("user_Login_token" + token);
        return Result.ok();
    }

    @Override
    public Result showUserPage(Long id) {
        User byId = getById(id);
        if (byId == null) {
            return Result.fail("出错！查询用户信息失败");
        }
        UserDTO userDTO = BeanUtil.copyProperties(byId, UserDTO.class);
        return Result.ok(userDTO);
    }

    @Override
    public Result userSign() {
        //在这里实现用户签到功能，基于redis中的bitMap数据结构实现，签到了置为1，未签到置为0
        //1.获取当前登录的用户信息
        UserDTO user = UserHolder.getUser();
        Calendar calendar = Calendar.getInstance();
        //获取当前月份作为key的一部分
        String month = String.valueOf(calendar.get(Calendar.MONTH) + 1);
        String key = "UserSign:" + user.getId().toString() + ":" + month;

        //获取当前天数，作为打卡添加的脚标
        int day = calendar.get(Calendar.DATE);
        stringRedisTemplate.opsForValue().setBit(key, day - 1, true);
        return Result.ok();
    }

    @Override
    public Result countSignDays() {
        Long id = UserHolder.getUser().getId();
        return countConsecutiveSign(id);
    }

    private Result countConsecutiveSign(Long userId) {
        //1.先获取当前用户的打卡bitMap
        Calendar calendar = Calendar.getInstance();
        //获取当前月份作为key的一部分
        String month = String.valueOf(calendar.get(Calendar.MONTH) + 1);
        String key = "UserSign:" + userId.toString() + ":" + month;
        int day = calendar.get(Calendar.DATE);
        List<Long> result = stringRedisTemplate
                .opsForValue()
                .bitField(
                        key,
                        BitFieldSubCommands.create()
                                .get(BitFieldSubCommands.BitFieldType.unsigned(day))
                                .valueAt(0));

        //非空判断
        if (result == null || result.isEmpty()) {
            return Result.ok();
        }
        Long signDays = result.get(0);
        if (signDays == null) {
            return Result.ok();
        }

        //循环遍历这个十进制signDays，让其最低位与二进制1做与运算，得到其位数，signday最低位比较完成后
        //便会舍弃当前最低位，次底位变成最低位，继续与二进制1进行与运算，得到其位数
        int countConsecutiveDay =0;
        while (true){
            if ((signDays & 1)==0){
                //出现0，则表示没有签到，连续签到结束
                break;
            }
            countConsecutiveDay++;
            //signDay的二进制无符号右移一位
            signDays >>>=1;
        }
        return Result.ok(countConsecutiveDay);
    }
}
